package com.my.spring.framework.aop.support;

import com.my.spring.framework.aop.config.MyAopConfig;
import com.my.spring.framework.aop.intercept.MyAfterReturningAdviceInterceptor;
import com.my.spring.framework.aop.intercept.MyMethodBeforeAdviceInterceptor;
import com.my.spring.framework.aop.intercept.MyThrowsAdviceInterceptor;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Getter
@Setter
public class MyAdvisedSupport {
    private MyAopConfig config;
    private Class<?> targetClass;
    private Object target;

    private Pattern pointCutClassPattern;

    private Map<Method, List<Object>> methodCache;

    public MyAdvisedSupport(MyAopConfig config) {
        this.config = config;
    }

    public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) throws NoSuchMethodException {
        List<Object> cached = methodCache.get(method);
        if (cached == null) {
            Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
            // ?????????
            cached = methodCache.get(m);
            methodCache.put(m, cached);
        }
        return cached;
    }

    public boolean pointCutMatch() {
        if (pointCutClassPattern == null) {
            parse();
        }
        return pointCutClassPattern.matcher(targetClass.toString()).matches();
    }

    private void parse() {
        String pointcut = config.getPointCut()
                .replaceAll("\\.", "\\\\.")
                .replaceAll("\\\\.\\*", ".*")
                .replaceAll("\\(", "\\\\(")
                .replaceAll("\\)", "\\\\)");
        String pointCutForClassRegex = pointcut.substring(0, pointcut.lastIndexOf("\\(") - 4);
        pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex
                .substring(pointCutForClassRegex.lastIndexOf(" ") + 1));

        try {
            methodCache = new HashMap<>();
            Pattern pattern = Pattern.compile(pointcut);

            Class<?> aspectClass = Class.forName(config.getAspectClass());
            Map<String, Method> aspectMethods = new HashMap<>();
            for (Method method : aspectClass.getMethods()) {
                aspectMethods.put(method.getName(), method);
            }

            for (Method method : targetClass.getMethods()) {
                String methodString = method.toString();
                if ((methodString.contains("throws"))) {
                    methodString = methodString.substring(0, methodString.lastIndexOf("throws")).trim();
                }
                Matcher matcher = pattern.matcher(methodString);
                if (matcher.matches()) {
                    List<Object> advices = new LinkedList<>();
                    if (!StringUtils.isEmpty(config.getAspectBefore())) {
                        Object aspectInstance = aspectClass.getConstructor().newInstance();
                        advices.add(new MyMethodBeforeAdviceInterceptor(aspectMethods.get(config.getAspectBefore()), aspectInstance));
                    }
                    if (!StringUtils.isEmpty(config.getAspectAfter())) {
                        Object aspectInstance = aspectClass.getConstructor().newInstance();
                        advices.add(new MyAfterReturningAdviceInterceptor(aspectMethods.get(config.getAspectAfter()), aspectInstance));
                    }
                    if (!StringUtils.isEmpty(config.getAspectAfterThrow())) {
                        Object aspectInstance = aspectClass.getConstructor().newInstance();
                        MyThrowsAdviceInterceptor throwsAdviceInterceptor = new MyThrowsAdviceInterceptor(aspectMethods.get(config.getAspectAfterThrow()), aspectInstance);
                        throwsAdviceInterceptor.setThrowName(config.getAspectAfterThrowingName());
                        advices.add(throwsAdviceInterceptor);
                    }
                    methodCache.put(method, advices);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
